package net.blogways.ordermgr.common.file;

import com.asiainfo.bpc.utils.ToolFactory;
import net.blogways.ordermgr.util.ErrorCode;
import net.blogways.ordermgr.util.MongoManager;
import net.blogways.ordermgr.util.Constants;
import com.asiainfo.utils.Utils;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSUploadOptions;
import com.mongodb.client.model.Filters;
import com.mongodb.client.result.UpdateResult;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * file operate command.
 *
 * @author chenlong
 * Created on 2018/3/2
 */
@Component("common:fileCommand")
public class FileCommand implements Command{

  private static final Logger log = (Logger) LoggerFactory.getLogger(FileCommand.class);

  @Override
  public boolean execute(Context context) throws Exception {
    return false;
  }

  /**
   * file uoload.
   *
   * @param context
   * @return
   * @throws IOException
   */
  public boolean fileUpload(Context context) throws IOException {

    String fileId = null;

    log.debug("Start Upload File Flow!");

    MongoManager manager = (MongoManager) ToolFactory.getInstance().getContext().getBean("mongoManager");

    MongoDatabase database = manager.getDatabase(context.get("MONGODB_NAME").toString());
    GridFSBucket bucket = GridFSBuckets.create(database);

//    ByteArrayInputStream stream = (ByteArrayInputStream) context.get("STREAM");
    ByteArrayInputStream stream = new ByteArrayInputStream(readStream((InputStream) context.get("STREAM")));

    GridFSUploadOptions options = new GridFSUploadOptions()
            .chunkSizeBytes(manager.getChunkSizeBytes())
            .metadata(
                    new Document(new HashMap<String, Object>(){
                      {
                        put("DELETE_TAG", Constants.AVAILABLE);
                        put("FILE_TYPE", context.get("FILE_TYPE"));
                        put("FILE_SIZE", context.get("FILE_SIZE"));
                      }
                    })
            );
    log.debug("start upload to mongodb");
    fileId = bucket
            .uploadFromStream(context.get("FILE_NAME").toString(), stream, options)
            .toString();
    log.debug("Return ObjectId: {}", fileId);
    context.put("FILE_ID", fileId);

    log.debug("end upload file flow");
    stream.close();
    return Utils.returnOkResult(context, "上传成功");
  }

  /**
   * file download.
   *
   * @param context
   * @return
   * @throws Exception
   */
  public boolean fileDownload(Context context) throws Exception {

    log.debug("start download file");
    MongoManager manager = (MongoManager) ToolFactory.getInstance().getContext().getBean("mongoManager");
    MongoDatabase database = manager.getDatabase(context.get("MONGODB_NAME").toString());

    log.debug("query file metadata document, param ObjectId: {}", context.get("OBJECT_ID").toString());
    Document document = database.getCollection(Constants.FS_METADATA_NAME).find(
            Filters.and(
                    Filters.eq("_id", new ObjectId(context.get("OBJECT_ID").toString())),
                    Filters.eq("metadata.DELETE_TAG", Constants.AVAILABLE)
            )
    ).first();

    log.debug("judge return document is empty, document.isEmpty(): {}", null == document || document.isEmpty());
    if (null == document || document.isEmpty()) {
      log.error("The Return Document of query file metadata is Empty!");
      return Utils.returnFalseResult(context, "0001", "指定文件为空");
    }

    context.put("FILE_NAME", document.get("filename"));
    context.put("FILE_TYPE", ((Map) document.get("metadata")).get("FILE_TYPE"));

    log.debug("start to get file  chuck");
    GridFSBucket bucket = GridFSBuckets.create(database);
    GridFSDownloadStream downloadStream = bucket
            .openDownloadStream(new ObjectId(context.get("OBJECT_ID").toString()));
    byte[] bytesToWriteTo = new byte[(int) downloadStream.getGridFSFile().getLength()];
    int readSize = downloadStream.read(bytesToWriteTo);

    log.debug("stream read result: {}", readSize);
    log.debug("will close download stream at once");
    downloadStream.close();

    context.put("DOWNLOAD_BYTES", bytesToWriteTo);
    log.debug("after set return Map, the download file flow end");

    return Utils.returnOkResult(context, "下载成功");
  }

  /**
   * file delete.
   *
   * @param context
   * @return
   */
  public boolean fileDelete(Context context) {

    log.debug("Start delete file flow");
    Map<String, Object> fs = (Map<String, Object>) ((Map<String, Object>) context.get("Base")).get("FS");
    List<String> fileIds = (List<String>) fs.get("OBJECT_IDS");
    MongoManager manager = (MongoManager) ToolFactory.getInstance().getContext().getBean("mongoManager");
    MongoDatabase database = manager.getDatabase(fs.get("DB_NAME").toString());

    log.debug("FileIds: {}", fileIds);

    Iterable<Bson> filters = new ArrayList<Bson>() {
      {
        for (String fileId: fileIds) {
          add(new Document("_id", new ObjectId(fileId)));
        }
      }
    };

    log.debug("Will delete file at MongoDB Server at once");
    UpdateResult updateResult = database.getCollection(Constants.FS_METADATA_NAME).updateOne(
            Filters.and(filters),
            new Document()
                    .append("$set", new Document("metadata.DELETE_TAG", 1))
    );
    log.debug("Delete Result is {}", updateResult);
    return Utils.returnOkResult(context, "删除成功");
  }

  /**
   * InputStream -> byte[]
   *
   * @param inStream
   * @return
   * @throws Exception
   */
  public static byte[] readStream(InputStream inStream) throws IOException{
    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int len = -1;
    while((len = inStream.read(buffer)) != -1){
      outStream.write(buffer, 0, len);
    }
    outStream.close();
    inStream.close();
    return outStream.toByteArray();
  }

  /**
   * file type check.
   * @param context
   * @return
   * @throws Exception
   */
  public boolean uploadCheck(Context context) throws Exception{

    String fileType = context.get("FILE_TYPE").toString();

    log.debug("************** uploadCheck fileType = {} **************", fileType);

    if (!Constants.FILE_TYPE.contains(fileType.toUpperCase())){
      log.error("fileType is not supported.");
      return Utils.returnFalseResult(context, ErrorCode.FILE_TYPE_ERROR, "上传文件格式不正确");
    }

    return Utils.returnOkResult(context, "操作成功");
  }

}
